#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <malloc.h>
#include "hash.h"

#include "vm.h"
#include "gc.h"

int tiny_value_compare(tiny_value *a,tiny_value *b){
    if(is_integer(a)){
        if(is_integer(b)){
            return a->value.int_ > b->value.int_ ?1:(a->value.int_ == b->value.int_?0:-1 );
        }else if(is_double(b)){
            return a->value.int_ > b->value.double_ ?1:(a->value.int_ == b->value.double_?0:-1 );
        }
    }else if(is_double(a)){
        if(is_integer(b)){
            return a->value.double_ > b->value.int_ ?1:(a->value.double_ == b->value.int_?0:-1 );
        }else if(is_double(b)){
            return a->value.double_ > b->value.double_ ?1:(a->value.double_ == b->value.double_?0:-1 );
        }
    }else if(is_char(a) && is_char(b)){
        return a->value.char_ > b->value.char_ ?1:(a->value.char_ == b->value.char_?0:-1 );
    }else if(is_string(a) && is_string(b)){
        return strcmp(a->value.str,b->value.str);
    }
    //error
}

tiny_value *lookup(char *name, tiny_env *env) {
    tiny_value *tmp = NULL;
    while (env != NULL) {
        tmp = (tiny_value *) findFromHash(env->symbol_table, name);
        if (tmp != NULL) {
            return tmp;
        }
        env = env->up_level;
    }
    return NULL;
}

tiny_value *make_value(enum tiny_type type) {
    tiny_value *tmp = (tiny_value *) malloc(sizeof (tiny_value));
    tmp->type = type;
    tmp->tail = NULL;
    tmp->length = 0;
    tmp->next = NULL;
    return tmp;
}

tiny_env *make_env(tiny_env *env) {
    tiny_env *tmp = (tiny_env *) malloc(sizeof (tiny_env));
    tmp->symbol_table = initHNode();
    tmp->up_level = env;
    return tmp;
}

tiny_interp *make_interp() {
    tiny_interp *tmp = (tiny_interp *) malloc(sizeof (tiny_interp));
    tmp->nil.type = tiny_type_nil;
    tmp->top_level = make_env(NULL);
    return tmp;
}
void display_value(tiny_value *value, int level) {
    if (value == NULL) {
        return;
    }
    int i = level;
    while (i > 0) {
        printf("  ");
        --i;
    }
    tiny_value *tmp = value;
    switch (tmp->type) {
        case tiny_type_symbol:
            printf("|—<symbol %s>\n", tmp->value.str);
            break;
        case tiny_type_string:
            printf("|—<string %s>\n", tmp->value.str);
            break;
        case tiny_type_char:
            printf("|—<char>\n");
            break;
        case tiny_type_double:
            printf("|—<double %f>\n", tmp->value.double_);
            break;
        case tiny_type_integer:
            printf("|—<integer %d>\n", tmp->value.int_);
            break;
        case tiny_type_nil:
            printf("|—<nil>\n");
            break;
        case tiny_type_bool:
            if(tmp->value.bool_){
                printf("|—<bool #t>\n");
            }else{
                printf("|—<bool #f>\n");
            }
            break;
        case tiny_type_list:
            printf("|—<list>\n");
            display_value(value->value.list, level + 1);
            break;
    }
    display_value(value->tail, level);
}

tiny_value *make_lambda(tiny_value *lambda) {
    tiny_value *val = make_value(tiny_type_procedure);
    tiny_value *args = lambda;
    tiny_value *content = lambda->tail;
    val->value.lambda.args = args;
    val->value.lambda.func = content;
    return val;
}

void set_env(tiny_env *env, char *name, tiny_value *val) {
    setToHash(env->symbol_table, name, val);
}

/**
ast intepreter
 */
tiny_value *tiny_eval(tiny_interp *vm, tiny_value *code, tiny_env *env) {
    tiny_value *tmp = NULL;
    tiny_value *args = NULL;
    tiny_value *arg = NULL;
    tiny_value *value = NULL;
    if (code == NULL) {
        return NULL;
    }
    switch (code->type) {
        case tiny_type_symbol:
            tmp = lookup(code->value.str, env);
            if (tmp == NULL) {
                printf("cant find var %s", code->value.str);
                exit(1);
            }
            return tmp;
            break;
        case tiny_type_string:
            return code;
            break;
        case tiny_type_char:
            return code;
            break;
        case tiny_type_double:
            return code;
            break;
        case tiny_type_integer:
            return code;
            break;
        case tiny_type_nil:
            return code;
            break;
        case tiny_type_list:
            tmp = code->value.list;
            if (tmp->type == tiny_type_symbol) {
                if (strcmp("lambda", tmp->value.str) == 0) {
                    return make_lambda(tmp->tail);
                } else if (strcmp("begin", tmp->value.str) == 0) {
                } else if (strcmp("define", tmp->value.str) == 0) {
                } else if (strcmp("quote", tmp->value.str) == 0) {
                } else {
                    tmp = tiny_eval(vm, tmp, env);
                    if (tmp == NULL) {
                        printf("cant find %s\n", code->value.list->value.str);
                        exit(1);
                    }
                    if (tmp->type == tiny_type_procedure) {
                        if (code->value.list->tail == NULL) {
                            args = &(vm->nil);
                        } else {
                            args = make_value(tiny_type_list);
                            arg = NULL;
                            value = code->value.list->tail;
                            while (value != NULL) {
                                if (arg == NULL) {
                                    arg = tiny_eval(vm, value, env);
                                    args->value.list = arg;
                                } else {
                                    arg->tail = tiny_eval(vm, value, env);
                                }
                                args->length += 1;
                                value = value->tail;
                            }
                        }

                        return ((cfunc) (tmp->value.procedure))(vm, args, env);
                    } else if (tmp->type == tiny_type_lambda) {
                        if (code->value.list->tail == NULL) {
                            args = &(vm->nil);
                        } else {
                            args = make_value(tiny_type_list);
                            arg = NULL;
                            value = code->value.list->tail;
                            while (value != NULL) {
                                if (arg == NULL) {
                                    arg = tiny_eval(vm, value, env);
                                    args->value.list = arg;
                                } else {
                                    arg->tail = tiny_eval(vm, value, env);
                                }
                                args->length += 1;
                                value = value->tail;
                            }
                        }
                        return tiny_apply_lambda(vm, tmp, args, env);
                    }
                }
            } else {
                tmp = tiny_eval(vm, tmp, env);
                if (code->value.list->tail == NULL) {
                    args = &(vm->nil);
                } else {
                    args = make_value(tiny_type_list);
                    arg = NULL;
                    value = code->value.list->tail;
                    while (value != NULL) {
                        if (arg == NULL) {
                            arg = tiny_eval(vm, value, env);
                            args->value.list = arg;
                        } else {
                            arg->tail = tiny_eval(vm, value, env);
                        }
                        args->length += 1;
                        value = value->tail;
                    }
                }
                return tiny_apply_lambda(vm, tmp, args, env);
            }
            break;
    }
}

tiny_value *tiny_apply_lambda(tiny_interp *vm, tiny_value *lambda, tiny_value *args, tiny_env *env) {
    tiny_env *new_env = make_env(env);
    tiny_value *k = lambda->value.lambda.args;
    tiny_value *v = args;
    printf("=>args size %d\n", list_length(k));
    if (list_length(k) > 0) {
        k = k->value.list;
        v = v->value.list;
        while (k != NULL) {
            set_env(new_env, k->value.str, v);
            //printf("=>var %s",k->value.str);
            k = k->tail;
            v = v->tail;
        }
    }
    return tiny_eval(vm, lambda->value.lambda.func, new_env);
}

tiny_value *make_cfunc(cfunc f) {
    tiny_value *val = make_value(tiny_type_procedure);
    val->value.procedure = f;
    return val;
}

tiny_value *make_integer(int i) {
    tiny_value *val = make_value(tiny_type_integer);
    val->value.int_ = i;
    return val;
}
tiny_value *make_double(double v){
    tiny_value *val = make_value(tiny_type_double);
    val->value.double_ = v;
    return val;
}
tiny_value *make_bool(int b){
    tiny_value *val = make_value(tiny_type_bool);
    val->value.bool_ = b;
    return val;
}
tiny_value *make_string(char* s){
    tiny_value *val = make_value(tiny_type_string);
    int len = strlen(s);
    val->value.str = (char *)malloc(len+1);
    memset(val->value.str, 0, len+1);
    memcpy(val->value.str, s, len);
    return val;
}
tiny_value *make_char(char c){
    tiny_value *val = make_value(tiny_type_char);
    val->value.char_ = c;
    return val;
}

tiny_value *make_string_port(tiny_value *str) {
    tiny_value *val = make_value(tiny_type_port);
    val->value.port = (tiny_port *) malloc(sizeof (tiny_port));
    val->value.port->type = port_string;
    return val;
}

void regist_global(tiny_interp *vm, char *name, tiny_value *v) {
    setToHash(vm->top_level->symbol_table, name, v);
}

